Object relational mapping allows you to declare in which tables and columns of your database the entity data will be stored.
Object/relational tools allow you to work with objects instead of tables and columns, and to generate automatically the SQL code to read and update the database. In this way you do not need direct access to the SQL database. Of course you have to define precisely how to map your classes to your tables, and this work is done using JPA mapping annotations.
The OpenXava entities are JPA entities, therefore the object/relational mapping in OpenXava is done by means of the Java Persistence API (JPA). This chapter shows the more basic mapping techniques and some special cases. If you want to learn more about JPA you can look at the documentation of Hibernate Annotations (the JPA implementation used by OpenXava by default), or whatever JPA manual you want.
Entity mapping
The @Table annotation specifies the primary table for the annotated entity. Additional tables may be specified using @SecondaryTable or @SecondaryTables annotation.
If no @Table annotation is specified for an entity class, the default values apply.
Example:
The @Column annotation is used to specify a mapped column for a persistent property or field. If no @Column annotation is specified, the default values are applied.
A simple example:
If you need to define a mapping for the composite foreign keys use @JoinColumns. This annotation groups @JoinColumn annotations for the same reference.
When the @JoinColumns annotation is used, both the name and the referencedColumnName elements must be specified in each such @JoinColumn annotation.
Example:
When you use @OneToMany for a collection the mapping depends of the reference used in the other part of the association, that is, usually it's not needed to do anything. But if you are using @ManyToMany, maybe it's useful to declare the @JoinTable, as following:
If @CollectionTable and @AttributeOverrides are missing the default values apply.
Embedded reference mapping
An embedded reference contains data that in the relational model are stored in the same table as the main entity. For example, if you have an embeddable Address associated to a Customer, the address data is stored in the same data table as the customer data. How can you map this case with JPA?
Just using @AttributeOverrides annotations, in this way:
If you do not use @AttributeOverrides default values are assumed.
Type conversion
The type conversion between Java and the relational database is a work for the JPA implementation (OpenXava uses Hibernate by default). Usually, the default type conversion is good for most cases, but if you work with legacy database perhaps you need to use the tips here.
Given that OpenXava uses the type conversion facility provided by Hibernate you can learn more on Hibernate documentation.
Property conversion
When the type of a Java property and the type of its corresponding column in DB do not match you need to write a Hibernate Type in order to do your custom type conversion.
For example, if you have a property of type String [], and you want to store its value concatenating it in a single table column of VARCHAR type. Then you must declare the conversion for your property in this way:
The type converter has to implement org.hibernate.usertype.UserType (1). The main methods are nullSafeGet (2) for read from database and to convert to Java, and nullSafeSet (3) for writing the Java value into database.
OpenXava has generic Hibernate type converters in the org.openxava.types package ready to use. One of them is EnumLetterType, that allows to map properties of enum type. For example, if you have a property like this:
private Distance distance;publicenum Distance { LOCAL, NATIONAL, INTERNATIONAL };
In this Java property 'LOCAL' is 1, 'NATIONAL' is 2 and 'INTERNATIONAL' is 3 when the property is stored in database. But what happens, if in the database a single letter ('L', 'N' or 'I') is stored? In this case you can use EnumLetterType in this way:
As you put 'LNI' as a value to letters, the type converter matches the 'L' to 1, the 'N' to 2 and the 'I' to 3. You also see how type converters are configurable using its properties and this makes the converters more reusable.
Multiple column conversion
With CompositeUserType you can map several table columns to a single Java property. This is useful if you have properties of custom class that have itself several attributes to store. Also it is used when you have to deal with legacy database schemes.
A typical example is the generic converter Date3Type, that allows to store in the database 3 columns and in Java a single property of type java.util.Date.
DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that store the delivery date. And here Date3Type:
packageorg.openxava.types;importjava.io.*;importjava.sql.*;importorg.hibernate.*;importorg.hibernate.engine.*;// Until OpenXava 5.2.ximportorg.hibernate.engine.spi.*;// Since OpenXava 5.3 that uses Hibernate 4.3importorg.hibernate.type.*;importorg.hibernate.usertype.*;importorg.openxava.util.*;/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/publicclass Date3Type implements CompositeUserType {// 1publicString[] getPropertyNames(){returnnewString[]{"year", "month", "day"};}publicType[] getPropertyTypes(){// return new Type[] { Hibernate.INTEGER, Hibernate.INTEGER, Hibernate.INTEGER }; // Before OpenXava 5.3/Hibernate 4.3returnnewType[]{ IntegerType.INSTANCE, IntegerType.INSTANCE, IntegerType.INSTANCE};// Since OpenXava 5.3/Hibernate 4.3}publicObject getPropertyValue(Object component, int property)throws HibernateException {// 2
java.util.Date date = (java.util.Date) component;switch(property){case0:
return Dates.getYear(date);case1:
return Dates.getMonth(date);case2:
return Dates.getDay(date);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicvoid setPropertyValue(Object component, int property, Object value)throws HibernateException // 3{
java.util.Date date = (java.util.Date) component;int intValue = value == null?0:((Number) value).intValue();switch(property){case0:
Dates.setYear(date, intValue);case1:
Dates.setMonth(date, intValue);case2:
Dates.setDay(date, intValue);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicClass returnedClass(){return java.util.Date.class;}publicboolean equals(Object x, Object y)throws HibernateException {if(x==y)returntrue;if(x==null || y==null)returnfalse;return!Dates.isDifferentDay((java.util.Date) x, (java.util.Date) y);}publicint hashCode(Object x)throws HibernateException {return x.hashCode();}publicObject nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)throws HibernateException, SQLException// 4{/* Before OpenXava 5.3/Hibernate 4.3
Number year = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[0] );
Number month = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[1] );
Number day = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[2] );
*/// Since OpenXava 5.3/Hibernate 4.3Number year = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[0], session, owner);Number month = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[1], session, owner );Number day = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[2], session, owner );int iyear = year == null?0:year.intValue();int imonth = month == null?0:month.intValue();int iday = day == null?0:day.intValue();return Dates.create(iday, imonth, iyear);}publicvoid nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)throws HibernateException, SQLException// 5{
java.util.Date d = (java.util.Date) value;/* Before OpenXava 5.3/Hibernate 4.3
Hibernate.INTEGER.nullSafeSet(st, Dates.getYear(d), index);
Hibernate.INTEGER.nullSafeSet(st, Dates.getMonth(d), index + 1);
Hibernate.INTEGER.nullSafeSet(st, Dates.getDay(d), index + 2);
*/// Since OpenXava 5.3/Hibernate 4.3
IntegerType.INSTANCE.nullSafeSet(st, Dates.getYear(d), index, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getMonth(d), index + 1, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getDay(d), index + 2, session);}publicObject deepCopy(Object value)throws HibernateException {
java.util.Date d = (java.util.Date) value;if(value == null)returnnull;return(java.util.Date) d.clone();}publicboolean isMutable(){returntrue;}publicSerializable disassemble(Object value, SessionImplementor session)throws HibernateException
{return(Serializable) deepCopy(value);}publicObject assemble(Serializable cached, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(cached);}publicObject replace(Object original, Object target, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(original);}}
As you see the type converter implements CompositeUserType (1). The key methods are getPropertyValue (2) and setPropertyValue (3) to get and to set values in the properties of the object of the composite type, and nullSafeGet (4) and nullSafeSet (5) for reading and storing this object from and to database.
Reference conversion
Reference conversion is not supported directly by Hibernate. But in some very rare circumstances maybe you need to do conversion in the reference. In this section we explain how to do it.
For example, you may have a reference to driver licence using two columns, DRIVINGLICENCE_LEVEL and DRIVINGLICENCE_TYPE, and the DRIVINGLICENCE_TYPE column does not admit null, but it's possible that the object can have no reference to driving lincence in which case the column DRIVINGLICENCE_TYPE hold an empty string. This is not a normal case if you design the database using foreign keys, but if the database was designed by a RPG programmer, for example, this was done in this way, because RPG programmer are not used to cope with nulls.
That is, you need a conversion for DRIVINGLICENCE_TYPE, for transform null to empty string. This can be achieve with a code like this:
// We apply conversion (null into an empty String) to DRIVINGLICENCE_TYPE column// In order to do it, we create drivingLicence_level and drivingLicence_type// We make JoinColumns not insertable nor updatable, we modify the get/setDrivingLincence methods// and we create a drivingLicenceConversion() method.
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({// 1
@JoinColumn(name="DRIVINGLICENCE_LEVEL", referencedColumnName="LEVEL",
insertable=false, updatable=false),
@JoinColumn(name="DRIVINGLICENCE_TYPE", referencedColumnName="TYPE",
insertable=false, updatable=false)})private DrivingLicence drivingLicence;privateInteger drivingLicence_level;// 2privateString drivingLicence_type;// 2public DrivingLicence getDrivingLicence(){// 3// In this way because the column for type of driving lincence does not admit nulltry{if(drivingLicence != null) drivingLicence.toString();// to force loadreturn drivingLicence;}catch(EntityNotFoundException ex){returnnull;}}publicvoid setDrivingLicence(DrivingLicence licence){// 4// In this way because the column for type of driving lincence does not admit nullthis.drivingLicence = licence;this.drivingLicence_level = licence==null?null:licence.getLevel();this.drivingLicence_type = licence==null?null:licence.getType();}
@PrePersist @PreUpdate
privatevoid drivingLicenceConversion(){// 5if(this.drivingLicence_type == null)this.drivingLicence_type = "";}
First, you have to use @JoinColumns with insertable=false and updatable=false on all @JoinColumn (1), in this way the reference is readed from database, but it is not write. Also define plain properties for storing the foreign key of the reference (2).
Now you must write a getter, getDrivingLicence() (3), for returning null when the reference is not found; and a setter, setDrivingLicence() (4), for assigning the key of the reference to the correspoding plain properties.
Finally, you have to write a callback method, drivingLincenceConversion() (5), to do the conversion work. This method will be automatically executed on create and update.
This example shows how it's possible to wrap legacy databases simply using a little of programming and some basic resources from JPA.
Unique Constraints
Since v4.9 openxava lets you to customize messages of declared constraints in the element uniqueConstraints of @Table and @SecondaryTable, as well as @Column(unique = true). To do this, keep in mind some preliminary considerations regarding Hibernate (The JPA implementation used by default by openxava).
Example:
By building our application (running build.xml), Hibernate will use the declared dialect in the persistence.xml of your project, to generate a DDL to map the structure of your classes to tables in the database. If, for example, you are working with a MySQL database, display:
createtable APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32)notnull,primary key(id),unique(name, birthdate, sex))createtable ApplicationUser (
id varchar(32)notnull,
nic varchar(8)unique,primary key(id))altertable APPLICATIONUSER_INFO
addindex FK375C9572BA846971 (id),addconstraint FK375C9572BA846971 foreign key(id)references ApplicationUser (id)
As can be seen Hibernate has mapped the structure of our ApplicationUser class has even created the constraint unique(name, birthdate, sex) and nic varchar (8) unique but did not assign declared name ("no_repeat_user_info") in @UniqueConstraint nor there is an element in @Column that allows us name the constraint unique=true, letting the database engine assigns default names.
When a violation of any of the above constraints occur, Hibernate will manage the error thrown by the database engine, creating a org.hibernate.exception.ConstraintViolationException or in some cases a org.hibernate.exception.GenericJDBCException -as is the HSQL. ConstraintViolationException has a constraintName property that is assigned by Hibernate after extracting the constraint name that has the database.
The dialect defined in your project is responsible for extract the constraint name to be assigned to contraintName. It does this through the extractConstraintName(SQLException sqle) method of ViolatedConstraintNameExtracter interface. But it happens that the dialects provided by Hibernate not always carried out properly constraintName extraction. For our example, MySQL5 dialect of Hibernate -Hibernate3.6.10 using openxava- nor implements ViolatedConstraintNameExtracter interface -Hibernate4 already does it adequately.
Therefore, if We want to generate a custom message to the above constraints, We do:
To maintain consistency between the name of the @UniqueConstraint and the name of the constraint in the database, wich Hibernate will manage through the constraintName of ConstraintViolationException, mapping manually the name of the constraint in the database:
createtable APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32)notnull,primary key(id),uniquekey`not_repeat_user_info`(name, birthdate, sex))
Same for the constraint of @Column:
createtable ApplicationUser (
id varchar(32)notnull,
nic varchar(8),primary key(id),uniquekey`not_repeat_nic`(nic))
Table of Contents
Object/relational mapping
Object relational mapping allows you to declare in which tables and columns of your database the entity data will be stored.Object/relational tools allow you to work with objects instead of tables and columns, and to generate automatically the SQL code to read and update the database. In this way you do not need direct access to the SQL database. Of course you have to define precisely how to map your classes to your tables, and this work is done using JPA mapping annotations.
The OpenXava entities are JPA entities, therefore the object/relational mapping in OpenXava is done by means of the Java Persistence API (JPA). This chapter shows the more basic mapping techniques and some special cases. If you want to learn more about JPA you can look at the documentation of Hibernate Annotations (the JPA implementation used by OpenXava by default), or whatever JPA manual you want.
Entity mapping
The @Table annotation specifies the primary table for the annotated entity. Additional tables may be specified using @SecondaryTable or @SecondaryTables annotation.If no @Table annotation is specified for an entity class, the default values apply.
Example:
Property mapping
The @Column annotation is used to specify a mapped column for a persistent property or field. If no @Column annotation is specified, the default values are applied.A simple example:
An example annotating the getter:
Other examples:
Reference mapping
The @JoinColumn annotation is used to specify a mapped column for a reference.Example:
If you need to define a mapping for the composite foreign keys use @JoinColumns. This annotation groups @JoinColumn annotations for the same reference.
When the @JoinColumns annotation is used, both the name and the referencedColumnName elements must be specified in each such @JoinColumn annotation.
Example:
Collection mapping
When you use @OneToMany for a collection the mapping depends of the reference used in the other part of the association, that is, usually it's not needed to do anything. But if you are using @ManyToMany, maybe it's useful to declare the @JoinTable, as following:If @JoinTable is missing the default values apply.
When you use @ElementCollection (new in v5.0) for a collection you can use @CollectionTable and @AttributeOverrides, as following:
If @CollectionTable and @AttributeOverrides are missing the default values apply.
Embedded reference mapping
An embedded reference contains data that in the relational model are stored in the same table as the main entity. For example, if you have an embeddable Address associated to a Customer, the address data is stored in the same data table as the customer data. How can you map this case with JPA?Just using @AttributeOverrides annotations, in this way:
If you do not use @AttributeOverrides default values are assumed.
Type conversion
The type conversion between Java and the relational database is a work for the JPA implementation (OpenXava uses Hibernate by default). Usually, the default type conversion is good for most cases, but if you work with legacy database perhaps you need to use the tips here.Given that OpenXava uses the type conversion facility provided by Hibernate you can learn more on Hibernate documentation.
Property conversion
When the type of a Java property and the type of its corresponding column in DB do not match you need to write a Hibernate Type in order to do your custom type conversion.For example, if you have a property of type String [], and you want to store its value concatenating it in a single table column of VARCHAR type. Then you must declare the conversion for your property in this way:
The conversion logic in RegionsType is:
The type converter has to implement org.hibernate.usertype.UserType (1). The main methods are nullSafeGet (2) for read from database and to convert to Java, and nullSafeSet (3) for writing the Java value into database.
OpenXava has generic Hibernate type converters in the org.openxava.types package ready to use. One of them is EnumLetterType, that allows to map properties of enum type. For example, if you have a property like this:
In this Java property 'LOCAL' is 1, 'NATIONAL' is 2 and 'INTERNATIONAL' is 3 when the property is stored in database. But what happens, if in the database a single letter ('L', 'N' or 'I') is stored? In this case you can use EnumLetterType in this way:
As you put 'LNI' as a value to letters, the type converter matches the 'L' to 1, the 'N' to 2 and the 'I' to 3. You also see how type converters are configurable using its properties and this makes the converters more reusable.
Multiple column conversion
With CompositeUserType you can map several table columns to a single Java property. This is useful if you have properties of custom class that have itself several attributes to store. Also it is used when you have to deal with legacy database schemes.A typical example is the generic converter Date3Type, that allows to store in the database 3 columns and in Java a single property of type java.util.Date.
DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that store the delivery date. And here Date3Type:
As you see the type converter implements CompositeUserType (1). The key methods are getPropertyValue (2) and setPropertyValue (3) to get and to set values in the properties of the object of the composite type, and nullSafeGet (4) and nullSafeSet (5) for reading and storing this object from and to database.
Reference conversion
Reference conversion is not supported directly by Hibernate. But in some very rare circumstances maybe you need to do conversion in the reference. In this section we explain how to do it.For example, you may have a reference to driver licence using two columns, DRIVINGLICENCE_LEVEL and DRIVINGLICENCE_TYPE, and the DRIVINGLICENCE_TYPE column does not admit null, but it's possible that the object can have no reference to driving lincence in which case the column DRIVINGLICENCE_TYPE hold an empty string. This is not a normal case if you design the database using foreign keys, but if the database was designed by a RPG programmer, for example, this was done in this way, because RPG programmer are not used to cope with nulls.
That is, you need a conversion for DRIVINGLICENCE_TYPE, for transform null to empty string. This can be achieve with a code like this:
First, you have to use @JoinColumns with insertable=false and updatable=false on all @JoinColumn (1), in this way the reference is readed from database, but it is not write. Also define plain properties for storing the foreign key of the reference (2).
Now you must write a getter, getDrivingLicence() (3), for returning null when the reference is not found; and a setter, setDrivingLicence() (4), for assigning the key of the reference to the correspoding plain properties.
Finally, you have to write a callback method, drivingLincenceConversion() (5), to do the conversion work. This method will be automatically executed on create and update.
This example shows how it's possible to wrap legacy databases simply using a little of programming and some basic resources from JPA.
Unique Constraints
Since v4.9 openxava lets you to customize messages of declared constraints in the element uniqueConstraints of @Table and @SecondaryTable, as well as @Column(unique = true). To do this, keep in mind some preliminary considerations regarding Hibernate (The JPA implementation used by default by openxava).Example:
- By building our application (running build.xml), Hibernate will use the declared dialect in the persistence.xml of your project, to generate a DDL to map the structure of your classes to tables in the database. If, for example, you are working with a MySQL database, display:
As can be seen Hibernate has mapped the structure of our ApplicationUser class has even created the constraint unique(name, birthdate, sex) and nic varchar (8) unique but did not assign declared name ("no_repeat_user_info") in @UniqueConstraint nor there is an element in @Column that allows us name the constraint unique=true, letting the database engine assigns default names.Therefore, if We want to generate a custom message to the above constraints, We do:
... <property name="hibernate.dialect"value="dialect.XMySQL5Dialect"/> ...Other dialects